#include "OP_CreateSphere.h"
#include "../Geometries/PolygonMesh.h"

using namespace std;
using namespace CICore;
using namespace CIGeom;
using namespace CIView;

///<************************************************************************************************************>
///<************************************* region CONSTANTS DEFINITION ******************************************>
///<************************************************************************************************************>

const float CIOps::OP_CreateSphere::X = 0.525731112119133606f;
const float CIOps::OP_CreateSphere::Z = 0.850650808352039932f;

const unsigned int CIOps::OP_CreateSphere::ICOSA_TRIANGLES_INDICES[20][3] = {
    {0,  4,  1}, {0, 9,  4}, {9,  5, 4}, { 4, 5, 8}, {4, 8,  1}, 
    {8, 10,  1}, {8, 3, 10}, {5,  3, 8}, { 5, 2, 3}, {2, 7,  3}, 
    {7, 10,  3}, {7, 6, 10}, {7, 11, 6}, {11, 0, 6}, {0, 1,  6}, 
    {6,  1, 10}, {9, 0, 11}, {9, 11, 2}, { 9, 2, 5}, {7, 2, 11}
};

const float CIOps::OP_CreateSphere::ICOSA_VERTICES[12][3] = {
    { X,  0, -Z}, {-X,  0, -Z}, { X,  0,  Z}, {-X,  0,  Z},  
    { 0, -Z, -X}, { 0, -Z,  X}, { 0,  Z, -X}, { 0,  Z,  X},  
    {-Z, -X,  0}, { Z, -X,  0}, {-Z,  X,  0}, { Z,  X,  0}
};
/***************************************************************************************************************
************************************** endregion CONSTANTS DEFINITION ******************************************
****************************************************************************************************************/












/// <summary> 
/// Constructor 
/// </summary>
/// <param name="v"> A reference to the vector that contains the four 3D views pointers </param>
CIOps::OP_CreateSphere::OP_CreateSphere (vector<CIView::View*>& v)
: Operation (v), sphere (0), radius (0.002f), sphereDetail (1) {

    newSelectionMode = scene.GetSelectionMode();

    stacks = OperationManager::Stacks;
    slices = OperationManager::Slices;
}

/// <summary> 
/// Destructor 
/// </summary>
CIOps::OP_CreateSphere::~OP_CreateSphere() {
    if (status != COMPLETED)
        delete sphere;
}










///<************************************************************************************************************>
///<**************************************** region Utility Methods ********************************************>
///<************************************************************************************************************>

/// <summary> 
/// Method called to execute directly the operation 
/// </summary>
/// <returns> The operation status </returns>
int CIOps::OP_CreateSphere::Exec() {
    switch (status) {
        case INITIAL:
            status = WAITING_FOR_EVENTS;
            break;
    }
    return status;        
}

/// <summary> 
/// Method called to undo the operation 
/// </summary>
void CIOps::OP_CreateSphere::Undo() {
    status = UNDO;      

    scene.RemoveGeometry (sphere->GetName().c_str());   //Removes the geometry from the 3D scene
    RestoreOldSelection();                              //Restores the old selection status
    UpdateViews();                                      //Updates the 3D views
}

/// <summary> 
/// Method called to redo the operation 
/// </summary>
void CIOps::OP_CreateSphere::Redo() {
    status = COMPLETED;    

    scene.AddGeometry (sphere);                     //Re-adds the geometry in the 3D scene  
    RestoreNewSelection();                          //Restore the new selectio status
    UpdateViews();                                  //Updates the 3D views
}

/// <summary> 
/// Mouse Pressed event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse press event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Mouse Pressed event </param>
void CIOps::OP_CreateSphere::MousePressEvent (CIView::View* view, QMouseEvent* e) {
    float x = e->x();
    float y = e->y();
    
    SaveOldSelection();                             //Saves the old selection status
    scene.DeselectAllFaces();                       //Deselects all faces       //TODO -> Deselect all (included geometries and groups)

    view->WindowToWorldCoordinates (&x, &y);        //Unproject the mouse cursor coordinates

    Orientation& cameraOrient = view->GetCameraOrientation();

    //Computing the sphere centre according to the current camera of the view that has thrown the event
    switch (view->GetCameraType()) {
        case Camera::TOP:
            centre.x = x;
            centre.z = y;
            break;
        
        case Camera::FRONT:
            centre.x = x;
            centre.y = -y;
            break;
        
        case Camera::RIGHT:  
            centre.z = -x;          
            centre.y = -y;
            break;
         
        //case Camera::USER:
        //    //TODO
        //    //la z corrisponde (pi� o meno) alla y quando camera->orientation.x > 20 e < 150
        //    //la z corrisponde (pi� o meno) alla x quando camera->orientation.y > 20 e < 150
        //    //si pu� fare una specia di interpolazione
        //    float c = cos (float (cameraOrient.y)) * sin (float (cameraOrient.x));
        //    float s = sin (float (cameraOrient.y)) * cos (float (cameraOrient.x));

        //    centre.x = x;
        //    centre.y = -y;
        //    //centre.z = (-x*s + y*c);
        //    break;
    }

    unsigned int id = scene.GenerateNewGeometryID();      //Gets a reference to the number of geometries that have been added to the scene
    stringstream name;
    name << "Sphere" << id;                               //Sets the name of the new triangle mesh 

    sphere = new PolygonMesh (name.str());                      //Creates the sphere 

    //CreateSphereFromIcosahedron (sphereDetail, radius);     //Creates the sphere with triangles as faces, starting from the icosahedron vertices
    CreateSphereWithTriangles (stacks, slices, radius);         //Creates the sphere with triangles as faces

    scene.AddGeometry (sphere);                                 //Adds the sphere in the 3D scene

    UpdateViews();                                              //Updates the 3D views
}

/// <summary> 
/// Mouse Released event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse release event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Mouse Released event </param>
void CIOps::OP_CreateSphere::MouseReleaseEvent (CIView::View* view, QMouseEvent* e) {
    status = COMPLETED;
    
    view->SetPrimitivesSelected (true);                     //Signals that there is a primitive selected

    SaveNewSelection();                     //Saves the new selection status
    sphere->UpdateBoundingBox();            //Updates the sphere bounding box
}

/// <summary> 
/// Mouse Moved event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse move event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Mouse Moved event </param>
void CIOps::OP_CreateSphere::MouseMoveEvent (CIView::View* view, QMouseEvent* e) {
    float x = e->x();
    float y = e->y();

    view->WindowToWorldCoordinates (&x, &y);            //Unproject the mouse cursor coordinates

    Orientation& cameraOrient = view->GetCameraOrientation();

    Vector3D drag;

    //Computing the new radius value according to the mouse drag values
    switch (view->GetCameraType()) {
        case Camera::TOP:
            drag.x = abs(centre.x - x);
            drag.z = abs(centre.z - y);
            break;
        
        case Camera::FRONT:
            drag.x = abs(centre.x - x);
            drag.y = abs(centre.y + y);
            break;
        
        case Camera::RIGHT:          
            drag.z = abs(centre.z + x);  
            drag.y = abs(centre.y + y);
            break;
         
        //case Camera::USER:
        //    //TODO
        //    //la z corrisponde (pi� o meno) alla y quando camera->orientation.x > 20 e < 150
        //    //la z corrisponde (pi� o meno) alla x quando camera->orientation.y > 20 e < 150
        //    //si pu� fare una specia di interpolazione
        //    float c = cos (float (cameraOrient.y)) + sin (float (cameraOrient.x));
        //    float s = -sin (float (cameraOrient.y)) + cos (float (cameraOrient.x));
        //    
        //    drag.x = abs(centre.x - x);
        //    drag.y = abs(centre.y + y);
        //    drag.z = abs(drag.x*s - drag.y*c);
        //    break;
    }    

    float newRadius = drag.Length();    

    if (newRadius == 0) //If the new raduis it's equal to zero
        return;         //must return

    float scaleValue = newRadius / radius;

    radius = newRadius;

    //Scaling the sphere according to the new radius
    Matrix m1, m2, m3;
    m1.SetTranslation (-centre.x, -centre.y, -centre.z);
    m2.SetScaling (scaleValue, scaleValue, scaleValue);
    m3.SetTranslation (centre.x, centre.y, centre.z);
    
    Matrix m;
    m = m1*m2*m3;
    sphere->ApplyMatrix (m);

    UpdateViews();                                      //Updates the 3D views
}

/// <summary> 
/// Wheel event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse wheel event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Wheel event </param>
void CIOps::OP_CreateSphere::WheelEvent (CIView::View* view, QWheelEvent* e) {
    if ((e->delta() > 0 && sphereDetail == 4) || (e->delta() <= 1 && sphereDetail == 1))
        return;

    e->delta() > 0 ? sphereDetail++ : sphereDetail--;                       //Increases/Decreases the sphere triangle tessellation detail

    if (stacks*sphereDetail > 30 || slices*sphereDetail > 30)
        return;

    sphere->DeleteAllVertices();                                            //Deletes the old sphere vertices

    //CreateSphereFromIcosahedron (sphereDetail, radius);                         //Creates the sphere with triangles as faces, starting from the icosahedron vertices
    CreateSphereWithTriangles (stacks*sphereDetail, slices*sphereDetail, radius);   //Creates the sphere with triangles as faces

    UpdateViews();                                                          //Updates the 3D views
}
/***************************************************************************************************************
***************************************** endregion Utility Methods ********************************************
****************************************************************************************************************/









///<***********************************************************************************************************>
///<*********************************** region Triangles Tessellation *****************************************>
///<***********************************************************************************************************>

/// <summary> 
/// Method called to create the sphere faces recursively, 
/// as triangle tessellation, according to the detail and 
/// sphere radius received
/// </summary>
/// <param name="x"> An array of 3 elements that represents the coordinates of a vertex for the new face </param>
/// <param name="y"> An array of 3 elements that represents the coordinates of a vertex for the new face </param>
/// <param name="z"> An array of 3 elements that represents the coordinates of a vertex for the new face </param>
/// <param name="d"> The mesh detail </param>
/// <param name="r"> The sphere radius </param>
void CIOps::OP_CreateSphere::CreateRecursiveTriangles (const float* x,
                                                    const float* y, 
                                                    const float* z, 
                                                    unsigned int d, 
                                                    float r) {
    if (d == 0) {                                                       //If the level of detail desired has been reached        
        int x_index = sphere->AddVertex (x[0]*r, x[1]*r, x[2]*r);       //adds the vertices of the new face to the mesh, 
        int y_index = sphere->AddVertex (y[0]*r, y[1]*r, y[2]*r);       //and store temporary the vertices'indices inside
        int z_index = sphere->AddVertex (z[0]*r, z[1]*r, z[2]*r);       //the mesh vertices library
        
        VertexLib& sphereVertices = sphere->GetVertices();              //retrieves a reference to the sphere vertices library
        VertexLib vertices(3);                                          //creates a vertices library of 3 elements for the new face
        vertices[0] = sphereVertices[x_index];
        vertices[1] = sphereVertices[y_index];
        vertices[2] = sphereVertices[z_index];

        Face* f = new Face (vertices);                                  //creates the new mesh face
        f->ComputeNormal();                                             //computes the face normal (that will be used to define the interaction between the face material and the scene light)
        sphere->AddFace (f);                                            //adds the face to the mesh
    }
    else {                                                              //else
        unsigned short i;                                               //has to increment the sphere tessellation detail
        float xy[3], xz[3], yz[3];                                      //generating a triangle inside each old tessellation triangle,
                                                                        //producing 4 new triangles in place of each old tessellation triangle
        //Computing the centre of each edge of the current triangle
        for (i = 0; i < 3; i++) {
            xy[i] = (x[i]+y[i]) / 2.0f;
            xz[i] = (x[i]+z[i]) / 2.0f;
            yz[i] = (y[i]+z[i]) / 2.0f;
        }

        //Normalizing the vertices
        NormalizeVertex (xy);
        NormalizeVertex (xz);
        NormalizeVertex (yz);

        //Computing the new 4 triangles in place of the current triangle
        CreateRecursiveTriangles ( x, xy, xz, d-1, r);
        CreateRecursiveTriangles ( y, yz, xy, d-1, r);
        CreateRecursiveTriangles ( z, xz, yz, d-1, r);
        CreateRecursiveTriangles (xy, yz, xz, d-1, r);
    }
}

/// <summary> 
/// Method called to create the sphere faces 
/// </summary>
/// <param name="detail"> The mesh detail </param>
/// <param name="radius"> The sphere radius </param>
void CIOps::OP_CreateSphere::CreateSphereFromIcosahedron (unsigned int detail, float radius) {
    unsigned short i;

    for (i=0; i<20; i++)
        CreateRecursiveTriangles (                          //Calls the CreateRecursiveTriangle to create the sphere
            ICOSA_VERTICES[ICOSA_TRIANGLES_INDICES[i][0]],  //faces (as triangles) according to the detail and radius
            ICOSA_VERTICES[ICOSA_TRIANGLES_INDICES[i][1]],  //received. 
            ICOSA_VERTICES[ICOSA_TRIANGLES_INDICES[i][2]],  //NOTE: Uses the Icosahedron vertices to create the sphere faces
            detail, 
            radius
        );        

    //Translating the sphere vertices according to the position desidered
    Matrix m;
    m.SetTranslation (centre.x, centre.y, centre.z);
    sphere->ApplyMatrix (m);
}

/// <summary> 
/// Method called to create a sphere centered at the origin 
/// </summary>
/// <param name="stacks"> The number of stacks (for the sphere subdivision in faces) </param>
/// <param name="slices"> The number of slices (for the sphere subdivision in faces) </param>
/// <param name="radius"> The sphere radius </param>
void CIOps::OP_CreateSphere::CreateSphereWithTriangles (int stacks, int slices, float radius) {
    double stackAngle = PI / stacks;
    double sliceAngle = 2.0 * PI / slices;

    int i, j, v1_index, v2_index, v3_index;
    
    double a = 0;
    double b = a + stackAngle;
    double r0 = radius * sin(a);
    double r1 = radius * sin(b);
    float z0 = radius * cos(a);
    float z1 = radius * cos(b);

    double c = 0;
    float x = cos(c);
    float y = sin(c);
                                                
    v1_index = sphere->AddVertex (x * r0, y * r0, z0);                      
    v2_index = sphere->AddVertex (x * r1, y * r1, z1);           

    for (i=0; i<stacks; i++) {
        a = i * stackAngle;
        b = a + stackAngle;
        r0 = radius * sin(a);
        r1 = radius * sin(b);
        z0 = radius * cos(a);
        z1 = radius * cos(b);
        
        j = i ? 0 : 1;

        for (; j<=slices; j++) {
            c = j * sliceAngle;
            x = cos(c);
            y = sin(c);

            //Adding a face
            v3_index = sphere->AddVertex (x * r0, y * r0, z0);  
            AddFace (v1_index, v2_index, v3_index);
            v1_index = v3_index;

            //Adding another one 
            v3_index = sphere->AddVertex (x * r1, y * r1, z1);  
            AddFace (v1_index, v2_index, v3_index);
            v2_index = v3_index;
        }
    }

    //Translating the sphere vertices according to the position desidered
    Matrix m;
    m.SetTranslation (centre.x, centre.y, centre.z);
    sphere->ApplyMatrix (m);
}

/// <summary> 
/// Method called to add a face (triangle) to the sphere
/// </summary>
/// <param name="v1"> The index of the first vertex int the sphere vertex library </param>
/// <param name="v2"> The index of the second vertex int the sphere vertex library </param>
/// <param name="v3"> The index of the third vertex int the sphere vertex library </param>
void CIOps::OP_CreateSphere::AddFace (int v1, int v2, int v3) {
    if (v1 == v2 || v1 == v3 || v2 == v3)
        return;

    VertexLib vertices(3);
    VertexLib& sphereVertices = sphere->GetVertices();
    Face* newFace;

    vertices[0] = sphereVertices[v1];
    vertices[1] = sphereVertices[v2];
    vertices[2] = sphereVertices[v3];

    newFace = new Face (vertices);
    newFace->ComputeNormal();
    sphere->AddFace (newFace);
}
/***************************************************************************************************************
*********************************** endregion Triangles Tessellation *******************************************
****************************************************************************************************************/
